#include "stdafx.h"
#include "EdbData.h"

#include <pimstore.h>
#include <regext.h>
#include <windbase.h>

#ifdef _DEBUG
#include "crtdbg.h"
#endif

#define EDBDATA_REG_ROOT  HKEY_LOCAL_MACHINE
#define EDBDATA_REG_KEY   TEXT("System\\Platform")
#define EDBDATA_REG_VALUE TEXT("DataPath")
#define EDBDATA_DEF_DIR   TEXT("\\")
#define EDBDATA_PIM_VOL   TEXT("pim.vol")
#define EDBDATA_PIM_VOL_BUFFER (256*1024)

void PropertyValues::Free()
{ 
  if (pPropVals != NULL)
    LocalFree(pPropVals);
  pPropVals = NULL;
  nPropVals = 0;
}

HWND EdbData::hWnd = NULL;
CEGUID EdbData::guidVol;
bool EdbData::isVolMounted = false;
HANDLE EdbData::aDbHandlers[EDB_MAX];
DWORD  EdbData::aDbUsers[EDB_MAX];
CENOTIFYREQUEST EdbData::aNotifyReqs[EDB_MAX];

bool EdbData::Initialize(HWND hWindow)
{
  hWnd = hWindow;
  ZeroMemory(aDbHandlers, sizeof(aDbHandlers));
  ZeroMemory(aDbUsers, sizeof(aDbUsers));
  isVolMounted = false;

  for (int i = 0; i < EDB_MAX; i++)
  {
    aNotifyReqs[i].dwSize = sizeof(CENOTIFYREQUEST);
    aNotifyReqs[i].hwnd = hWnd;
    aNotifyReqs[i].dwFlags = CEDB_EXNOTIFICATION;
    aNotifyReqs[i].hHeap = 0;
    aNotifyReqs[i].dwParam = i;
  }

  return true;
}

void EdbData::Terminate()
{
  for (int i = 0; i < EDB_MAX; i++)
  {
    CloseDatabase((EDBASE)i, true);
  }

  if (isVolMounted)
    UnmountDbVol();
}

bool EdbData::OpenDatabase(EDBASE edb, bool sendNotifications)
{
  return OpenDatabase(edb, NULL, sendNotifications);
}

bool EdbData::OpenDatabase(EDBASE edb, SORTORDERSPECEX* pSortOrder, bool sendNotifications)
{
  HANDLE hdb;
  CEOID oiddb;

  if (edb < 0 || edb >= EDB_MAX)
  {
    return false;
  }

  // if sort order is given, then reopen the database
  if (pSortOrder != NULL)
    CloseDatabase(edb, true);

  if (aDbHandlers[edb] == NULL) 
  {
    // Only do this if we have not already 
    // opened this database.

    // Open the pim volume
    if (!MountDbVol())
    {
      return false;
    }

    // Open the database by name
    oiddb = 0;

    TCHAR szDbName[MAX_PATH];
    switch(edb)
    {
    case EDB_CONTACT:
      szDbName[0] = 0;
      break;
    case EDB_CLOG:
      StringCchCopy(szDbName, MAX_PATH, TEXT("clog.db"));
      break;
    case EDB_SCUT:
      StringCchCopy(szDbName, MAX_PATH, TEXT("speed.db"));
      break;
    case EDB_SIM:
      StringCchCopy(szDbName, MAX_PATH, TEXT("\\SimEntries"));
      break;
    }

    CENOTIFYREQUEST* pNotify = NULL;
    if (sendNotifications)
    {
      pNotify = &aNotifyReqs[edb];
    }

    // Open the database
    hdb = CeOpenDatabaseInSession(NULL, &guidVol, &oiddb, szDbName, pSortOrder, CEDB_AUTOINCREMENT, pNotify);

    // Cache for later use
    if (hdb != INVALID_HANDLE_VALUE)
    {
      aDbHandlers[edb] = hdb;
    }
  }

  if (aDbHandlers[edb] != NULL)
    aDbUsers[edb]++;

  return (aDbHandlers[edb] != NULL);
}

void EdbData::CloseDatabase(EDBASE edb, bool forcedClose)
{
  if (edb < 0 || edb >= EDB_MAX)
    return;

  if (aDbUsers[edb] > 0)
    aDbUsers[edb]--;
  if (aDbUsers[edb] == 0 || forcedClose)
  {
    if (aDbHandlers[edb] != NULL)
      CloseHandle(aDbHandlers[edb]);
    aDbHandlers[edb] = NULL;
    aDbUsers[edb] = 0;
  }
}

bool EdbData::FreeNotification(CENOTIFICATION* pNotification)
{
  if (pNotification)
  {
    if (pNotification->dwParam < 0 || pNotification->dwParam >= EDB_MAX)
      return false;
    return (CeFreeNotification(&aNotifyReqs[pNotification->dwParam], pNotification) == TRUE);
  }
  return true;
}

DWORD EdbData::GetNumRecords(EDBASE edb)
{
  if (edb < 0 || edb >= EDB_MAX)
    return 0;

  if (!IsOpened(edb))
    return 0;

  BY_HANDLE_DB_INFORMATION dbInfo;
  dbInfo.wVersion = 2;
  if (CeGetDBInformationByHandle(aDbHandlers[edb], &dbInfo))
  {
    return dbInfo.infDatabase.dwNumRecords;
  }
  return 0;
}

CEOID EdbData::SeekDatabase(EDBASE edb, DWORD dwSeekType, DWORD dwValue, WORD wNumVals, LPDWORD lpdwIndex)
{
  if (edb < 0 || edb >= EDB_MAX)
    return 0;

  if (!IsOpened(edb))
    return 0;

  return CeSeekDatabaseEx(aDbHandlers[edb], dwSeekType, dwValue, wNumVals, lpdwIndex);
}

CEOID EdbData::ReadRecord(EDBASE edb, CEOID oid, CEPROPVAL** pPropVals, ULONG& nPropVals, WORD& nPropCount)
{
  if (oid == 0)
    return 0;

  if (edb < 0 || edb >= EDB_MAX)
    return 0;

  if (!IsOpened(edb))
    return 0;

  CEOID oid2;
  oid2 = CeSeekDatabaseEx(aDbHandlers[edb], CEDB_SEEK_CEOID, (DWORD)oid, 0, NULL);
  if (oid2 == 0)
    return 0;

  return ReadRecord(edb, pPropVals, nPropVals, nPropCount);
}

CEOID EdbData::ReadRecord(EDBASE edb, CEOID oid, const CEPROPID* aPropIds, int nPropIds, CEPROPVAL** pPropVals, ULONG& nPropVals)
{
  if (oid == 0 || aPropIds == NULL || nPropIds == 0)
    return 0;

  if (edb < 0 || edb >= EDB_MAX)
    return 0;

  if (!IsOpened(edb))
    return 0;

  CEOID oid2;

  oid2 = CeSeekDatabaseEx(aDbHandlers[edb], CEDB_SEEK_CEOID, (DWORD)oid, 0, NULL);
  if (oid2 == 0)
    return 0;

  return ReadRecord(edb, aPropIds, nPropIds, pPropVals, nPropVals);
}

CEOID EdbData::ReadRecord(EDBASE edb, CEPROPVAL** pPropVals, ULONG& nPropVals, WORD& nPropCount)
{
  if (edb < 0 || edb >= EDB_MAX)
    return 0;

  if (!IsOpened(edb))
    return 0;

  CEOID oid2;

  nPropCount = 0;
  oid2 = CeReadRecordPropsEx(aDbHandlers[edb], CEDB_ALLOWREALLOC, &nPropCount, NULL, (BYTE**)pPropVals, &nPropVals, ::GetProcessHeap());
  return oid2;
}

CEOID EdbData::ReadRecord(EDBASE edb, const CEPROPID* aPropIds, int nPropIds, CEPROPVAL** pPropVals, ULONG& nPropVals)
{
  if (aPropIds == NULL || nPropIds == 0)
    return 0;

  if (edb < 0 || edb >= EDB_MAX)
    return 0;

  if (!IsOpened(edb))
    return 0;

  CEOID oid2;

  WORD wPropIds = nPropIds;
  oid2 = CeReadRecordPropsEx(aDbHandlers[edb], CEDB_ALLOWREALLOC, &wPropIds, const_cast<CEPROPID*>(aPropIds), (BYTE**)pPropVals, &nPropVals, ::GetProcessHeap());
  return oid2;
}

bool EdbData::WriteRecord(EDBASE edb, CEOID oid, CEPROPVAL* pPropVals, int nPropVals)
{
  if (pPropVals == NULL || nPropVals == 0)
    return false;

  if (edb < 0 || edb >= EDB_MAX)
    return false;

  if (!IsOpened(edb))
    return false;

  CEOID  oid2;
  oid2 = CeWriteRecordProps(aDbHandlers[edb], oid, nPropVals, pPropVals);
  return (oid2 != 0);
}

bool EdbData::DeleteRecord(EDBASE edb, CEOID oid)
{
  if (oid == 0)
    return false;

  if (edb < 0 || edb >= EDB_MAX)
    return false;

  if (!IsOpened(edb))
    return false;

  BOOL res = CeDeleteRecord(aDbHandlers[edb], oid);
  return (res == TRUE);
}

bool EdbData::IsPropertyEmpty(const CEPROPVAL& prop)
{
  if (prop.wFlags & CEDB_PROPNOTFOUND)
  {
    return true;
  }

  switch(LOWORD(prop.propid))
  {
  case CEVT_LPWSTR:
    return (prop.val.lpwstr == NULL || prop.val.lpwstr[0] == 0);
  }

  return false;
}

bool EdbData::IsValidString(const CEPROPVAL& prop)
{
  if (prop.wFlags & CEDB_PROPNOTFOUND)
    return false;

  if (LOWORD(prop.propid) != CEVT_LPWSTR)
    return false;

  return (prop.val.lpwstr != NULL && prop.val.lpwstr[0] != 0);
}

bool EdbData::GetPropertyValue(const CEPROPVAL& prop, TCHAR* buf, size_t maxLen)
{
  if (buf == NULL)
  {
    return false;
  }
  if (prop.wFlags & CEDB_PROPNOTFOUND)
  {
    return false;
  }

  switch(LOWORD(prop.propid))
  {
  case CEVT_BLOB:
    return false;
  case CEVT_BOOL:
    if (prop.val.boolVal)
      StringCchCopy(buf, maxLen, TEXT("TRUE"));
    else
      StringCchCopy(buf, maxLen, TEXT("FALSE"));
    return true;
  case CEVT_FILETIME:
    {
      SYSTEMTIME st;
      if (FileTimeToSystemTime(&prop.val.filetime, &st))
      {
        GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, buf, maxLen);
        return true;
      }
    }
    break;
  case CEVT_I2:
    StringCchPrintf(buf, maxLen, TEXT("%d"), prop.val.iVal);
    return true;
  case CEVT_I4:
    StringCchPrintf(buf, maxLen, TEXT("%ld"), prop.val.lVal);
    return true;
  case CEVT_R8:
    StringCchPrintf(buf, maxLen, TEXT("%I64d"), prop.val.dblVal);
    return true;    
  case CEVT_UI2:
    StringCchPrintf(buf, maxLen, TEXT("%u"), prop.val.uiVal);
    return true;
  case CEVT_UI4:
    StringCchPrintf(buf, maxLen, TEXT("%lu"), prop.val.ulVal);
    return true;
  case CEVT_LPWSTR:
    if (prop.val.lpwstr != NULL && prop.val.lpwstr[0] != 0)
    {
      StringCchCopy(buf, maxLen, prop.val.lpwstr);
      return true;
    }
    break;
  }

  return false;
}

bool EdbData::GetPropertyValue(const CEPROPVAL& prop, DWORD& value)
{
  if (prop.wFlags & CEDB_PROPNOTFOUND)
  {
    return false;
  }

  switch(LOWORD(prop.propid))
  {
  case CEVT_BLOB:
    return false;
  case CEVT_BOOL:
    if (prop.val.boolVal)
      value = 1;
    else
      value = 0;
    return true;
  case CEVT_FILETIME:
    return false;
  case CEVT_I2:
    value = (DWORD)prop.val.iVal;
    return true;
  case CEVT_I4:
    value = (DWORD)prop.val.lVal;
    return true;
  case CEVT_R8:
    return false;
  case CEVT_UI2:
    value = (DWORD)prop.val.uiVal;
    return true;
  case CEVT_UI4:
    value = (DWORD)prop.val.ulVal;
    return true;
  case CEVT_LPWSTR:
    return false;
  }

  return false;
}

bool EdbData::GetPropertyValue(const CEPROPVAL& prop, LONG& value)
{
  if (prop.wFlags & CEDB_PROPNOTFOUND)
  {
    return false;
  }

  switch(LOWORD(prop.propid))
  {
  case CEVT_BLOB:
    return false;
  case CEVT_BOOL:
    if (prop.val.boolVal)
      value = 1;
    else
      value = 0;
    return true;
  case CEVT_FILETIME:
    return false;
  case CEVT_I2:
    value = (LONG)prop.val.iVal;
    return true;
  case CEVT_I4:
    value = (LONG)prop.val.lVal;
    return true;
  case CEVT_R8:
    return false;
  case CEVT_UI2:
    value = (LONG)prop.val.uiVal;
    return true;
  case CEVT_UI4:
    value = (LONG)prop.val.ulVal;
    return true;
  case CEVT_LPWSTR:
    return false;
  }

  return false;
}

bool EdbData::GetPropertyValue(const CEPROPVAL& prop, INT& value)
{
  if (prop.wFlags & CEDB_PROPNOTFOUND)
  {
    return false;
  }

  switch(LOWORD(prop.propid))
  {
  case CEVT_BLOB:
    return false;
  case CEVT_BOOL:
    if (prop.val.boolVal)
      value = 1;
    else
      value = 0;
    return true;
  case CEVT_FILETIME:
    return false;
  case CEVT_I2:
    value = (INT)prop.val.iVal;
    return true;
  case CEVT_I4:
    value = (INT)prop.val.lVal;
    return true;
  case CEVT_R8:
    return false;
  case CEVT_UI2:
    value = (INT)prop.val.uiVal;
    return true;
  case CEVT_UI4:
    value = (INT)prop.val.ulVal;
    return true;
  case CEVT_LPWSTR:
    return false;
  }

  return false;
}

bool EdbData::GetPropertyValue(const CEPROPVAL& prop, FILETIME& value)
{
  if (prop.wFlags & CEDB_PROPNOTFOUND)
  {
    return false;
  }

  switch(LOWORD(prop.propid))
  {
  case CEVT_FILETIME:
    value = prop.val.filetime;
    return true;
  }

  return false;
}

bool EdbData::GetPropertyDateValue(const CEPROPVAL& prop, TCHAR* buf, size_t maxLen, DWORD dwFlags)
{
  if (buf == NULL)
    return false;
  if (prop.wFlags & CEDB_PROPNOTFOUND)
    return false;

  switch(LOWORD(prop.propid))
  {
  case CEVT_FILETIME:
    {
      SYSTEMTIME st;
      if (FileTimeToSystemTime(&prop.val.filetime, &st))
      {
        GetDateFormat(LOCALE_USER_DEFAULT, dwFlags, &st, NULL, buf, maxLen);
        return true;
      }
    }
    break;
  }
  return false;
}

bool EdbData::GetPropertyTimeValue(const CEPROPVAL& prop, TCHAR* buf, size_t maxLen, DWORD dwFlags)
{
  if (buf == NULL)
    return false;
  if (prop.wFlags & CEDB_PROPNOTFOUND)
    return false;

  switch(LOWORD(prop.propid))
  {
  case CEVT_FILETIME:
    {
      SYSTEMTIME st;
      if (FileTimeToSystemTime(&prop.val.filetime, &st))
      {
        GetTimeFormat(LOCALE_USER_DEFAULT, dwFlags, &st, NULL, buf, maxLen);
        return true;
      }
    }
    break;
  }
  return false;
}

bool EdbData::IsOpened(EDBASE edb)
{
  if (edb < 0 || edb >= EDB_MAX)
    return false;
  return (aDbHandlers[edb] != NULL);
}

bool EdbData::GetDataDirectory(TCHAR* szBuffer, DWORD nLength)
{
  if (!szBuffer || !nLength)
    return false;

  HRESULT hr = RegistryGetString(EDBDATA_REG_ROOT, EDBDATA_REG_KEY, EDBDATA_REG_VALUE, szBuffer, nLength);
  if (SUCCEEDED(hr))
  {
    if (szBuffer[_tcslen(szBuffer) - 1] != TEXT('\\'))
      StringCchCat(szBuffer, nLength, TEXT("\\"));
  }
  else
  {
    // Copy def dir to szBuffer
    StringCchCopy(szBuffer, nLength, EDBDATA_DEF_DIR);
  }
  return true;
}

bool  EdbData::MountDbVol()
{
  if (!isVolMounted)
  {
    TCHAR szVolume[MAX_PATH];
    CEVOLUMEOPTIONS moPim = { 0 };
    
    if (GetDataDirectory(szVolume, MAX_PATH))
    {
        moPim.wVersion = CEVOLUMEOPTIONS_VERSION;
        moPim.dwFlags = CEDB_BUFFERPOOL;
        moPim.cbBufferPool = EDBDATA_PIM_VOL_BUFFER;

        StringCchCat(szVolume, MAX_PATH, EDBDATA_PIM_VOL);
        
        isVolMounted = (CeMountDBVolEx(&guidVol, szVolume, &moPim, OPEN_EXISTING | EDB_MOUNT_FLAG) == TRUE);
    }     
  }

  return isVolMounted;
}

bool  EdbData::UnmountDbVol()
{
  if (isVolMounted)
  {
    isVolMounted = !(CeUnmountDBVol(&guidVol));
  }

  return !(isVolMounted);
}